package com.lgj.feign;

import com.alibaba.cloud.nacos.NacosServiceInstance;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


/**
 * @author: guangjie.liao
 * @Date: 2023/8/16 19:03
 * @Description: 灰度发布负载均衡策略
 */
@Slf4j
public class GrayscaleServiceInstanceLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private GrayscaleWhiteListUser grayWhiteListUser;

    /**
     * @param serviceInstanceListSupplierProvider a provider of
     * {@link ServiceInstanceListSupplier} that will be used to get available instances
     */
    public GrayscaleServiceInstanceLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                                GrayscaleWhiteListUser grayWhiteListUser) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.grayWhiteListUser = grayWhiteListUser;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        DefaultRequest req = (DefaultRequest) request;
        RequestDataContext context = (RequestDataContext) req.getContext();
        RequestData requestData = context.getClientRequest();
        String username = requestData.getHeaders().getFirst("username");
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next()
                .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances,username));
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
                                                              List<ServiceInstance> serviceInstances,
                                                              String username) {
        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances,username);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,String username) {
        if (instances.isEmpty()) {
            return new EmptyResponse();
        }
        List<ServiceInstance> grayscaleServers = new ArrayList<>();
        List<ServiceInstance> productServers = new ArrayList<>();
        for (ServiceInstance serverInfo : instances) {
            //获取nacos配置元素据
            NacosServiceInstance nacosServer = (NacosServiceInstance) serverInfo;
            String version = nacosServer.getMetadata().get("version");
            //根据版本号区分灰度还是生产
            if (!StringUtils.isEmpty(version) && version.equals(grayWhiteListUser.getGrayscaleVersion())) {
                grayscaleServers.add(nacosServer);
            }else{
                productServers.add(nacosServer);
            }
        }
        //请求头配置白名单用户，没有配置则访问生产环境
        //配置的灰度环境服务不为空并且当前配置用户在白名单中
        if (!CollectionUtils.isEmpty(grayscaleServers) && grayWhiteListUser.getUsers().contains(username)){
            return getDefaultResponse(grayscaleServers);
        }
        return getDefaultResponse(productServers);
    }

    private DefaultResponse getDefaultResponse(List<ServiceInstance> servers){
        //打乱随机取一个，也可以设置其他策略
        Collections.shuffle(servers);
        ServiceInstance server = servers.get(0);
        return new DefaultResponse(server);
    }

}
